package br.ufpe.cin.mac.middleware.connectors.metaconnector;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.management.MBeanServer;
import javax.management.Notification;
import javax.management.ObjectName;

import br.ufpe.cin.mac.middleware.exceptions.ObjectNameNotFoundException;
import br.ufpe.cin.mac.util.Constants;
import br.ufpe.cin.mac.util.Loader;
import br.ufpe.cin.mac.util.Message;

/**
 * Essa classe tem por finalidade gerenciar o envio e recebimento de dados
 * atrav�s do middleware. Ela recebe todas as tentativas de transa��es,
 * checa a conex�o e ent�o envia para o destino. Ela quem checa o tipo da
 * conex�o a ser enviada
 * 
 * @author Osmany
 */

@SuppressWarnings("unchecked")
public class Configurator implements ConfiguratorMBean {

	private MBeanServer mbserver;

	private Map<Integer, Message> proxyBuffer;

	private String conn;

	public Configurator(MBeanServer mbserver, String conn) {
		try {
			this.mbserver = mbserver;
			this.proxyBuffer = Collections
					.synchronizedMap(new HashMap<Integer, Message>());
			this.conn = conn;
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Recebe as notifica��es provenientes dos outros MBeans e encaminha os
	 * dados para os interceptors necess�rios
	 */
	public void handleNotification(final Notification not, Object arg1) {

		Thread t = new Thread() {
			public void run() {
				// TODO Remover
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				processMsg(not);
			}
		};
		t.start();

	}

	private void processMsg(Notification not) {
		/*
		 * Obtenho o tipo da notifica��o pra determinar o destino do pacote:
		 * As notifica��es podem ser: Components: - invU - terP Connectors: -
		 * invS - terC
		 */
		Message msg = null;
		try {
			// Position[0] source; Position[1] port
			String[] source_type = not.getType().split(":");

			ObjectName objName = new ObjectName(this
					.getObjectName(source_type[0]));

			String[] sig = new String[] { int.class.getName() };
			Object[] param = new Object[] { not.getUserData() };
			// Acessa a porta e obtem a mensagem

			msg = (Message) this.mbserver.invoke(objName, source_type[1],
					param, sig);

			if (msg != null) {

				String interceptor = msg.readInterceptor();

				/* TEMPOR�RIO */
				if (msg.getType().equals(Constants.ACTION_CHECKER)) {
					if (msg.getMethod()
							.equals(Constants.METH_CHANGEINTERCEPTOR)) {

						String oldMbean = (String) msg.getParameter()[0];
						String newMbean = (String) msg.getParameter()[1];
						this.unregisterMBean(oldMbean);
						this.registerMBean(newMbean, oldMbean);
					} else if (msg.getMethod().equals(
							Constants.METH_GET_INTERCEPTORS)) {

						List<String> list = this.getInterceptors();
						msg.setParameter(new Object[] { list });

						this.mbserver.invoke(new ObjectName(msg.getSender()),
								"invP", new Object[] { msg },
								new String[] { Message.class.getName() });
					}
				}

				else if ("Proxy".equals(interceptor)) {
					synchronized (proxyBuffer) {
						proxyBuffer.put(msg.getIndex(), msg);
					}

				} else {
					// Verifica se j� � o fim dos interceptadores para levar
					// a
					// mensagem ao seu conector
					if ("Empty".equals(interceptor)) {
						// interceptor = msg.getConnector();
					}
					// Verifica a porta a ser acessada no pr�ximo
					// interceptador
					else {
						String port = "";
						if (isConnector(interceptor)) {
							// ida do pacote
							if (msg.getType().equals(Constants.REQUEST))
								port = Constants.PORT_INVC;
							else if (msg.getType().equals(Constants.RESPONSE))
								port = Constants.PORT_TERS; // volta do pacote

						} else {
							/* Mensagem pra componente */
							/* Se um componente que chama o outro */
							if (msg.getMessage().equals(Constants.PORT_TERU))
								port = Constants.PORT_TERU;
							else
								port = Constants.PORT_INVP;
						}
						// Envia o pacote pra porta do interceptador
						// List ops = null;// msg.readOperations(interceptor);
						/*
						 * String in = interceptor.substring(interceptor
						 * .lastIndexOf("=") + 1); if (in.indexOf("Server") < 0)
						 * System.out.println(in);
						 */
						System.out.println(interceptor);
						ObjectName intObjName = new ObjectName(interceptor);
						// Verifica se o interceptador esta registrado
						if (!this.mbserver.isRegistered(intObjName)) {

							// Tenta pegar o componente
							Object obj = Loader.getComponent(interceptor);
							if (obj == null)
								// Caso nao consiga, obtem-se o conector
								obj = Loader.getConcreteConnector(interceptor);

							this.mbserver.registerMBean(obj, intObjName);
							this.mbserver.addNotificationListener(intObjName,
									new ObjectName(this
											.getObjectName("Configurator")),
									null, null);
						}
						this.mbserver.invoke(intObjName, port,
								new Object[] { msg },
								new String[] { Message.class.getName() });
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// Autonomic Computing
	private void registerMBean(String newMBeanName, String oldMBeanName) {
		try {
			ObjectName newObjName = new ObjectName(newMBeanName);

			ObjectName conObjName = null;
			Object obj = null;
			conObjName = new ObjectName(this.conn);
			// this.mbserver.getObjectInstance(conObjName);

			// Se for componente
			if (!isConnector(newMBeanName)) {
				obj = Loader.getComponent(newMBeanName);
			} else {
				obj = Loader.getConcreteConnector(newMBeanName);
			}
			this.mbserver.invoke(conObjName, Constants.METH_CHANGEINTERCEPTOR,
					new Object[] { oldMBeanName, newMBeanName }, new String[] {
							"java.lang.String", "java.lang.String" });
			this.mbserver.registerMBean(obj, newObjName);
			this.mbserver.addNotificationListener(newObjName, new ObjectName(
					this.getObjectName("Configurator")), null, null);
			// if (conObjName != null) {
		
			// }
		} catch (Exception e) {
			//e.printStackTrace();
		}
	}

	private void unregisterMBean(String mbeanName) {
		try {
			if (!isConnector(mbeanName)) {
				ObjectName objName = new ObjectName(mbeanName);
				if (this.mbserver.isRegistered(objName)) {
					this.mbserver.unregisterMBean(objName);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * NO momento em que o m�todo send � chamado, ele l� novamente o
	 * arquivo de configura��o. Essa abordagem precisa ser ajustada pra
	 * controlar melhor esse acesso ao arquivo
	 */

	public void invC(Message msg) {

		try {

			msg.setConnector(this.conn);

			this.invoke(msg);

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Response
	 * 
	 * @param msg
	 */
	public void terS(Message msg) {
		try {

			String connector = msg.getConnector();
			List<String> ops = new LinkedList<String>();
			ops.add("sendResponse");
			msg.addInterceptor("Proxy", ops); // ??????????????
			Object[] param = new Object[] { msg };
			String[] sig = new String[] { msg.getClass().getName() };

			// Invoca��o do m�todo do Connector escolhido
			this.mbserver.invoke(new ObjectName(connector), "terS", param, sig);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public Message invS(int hashMsg) {
		Message msg = null;

		return msg;
	}

	public Message terC(int hashMsg) {
		Message msg = null;

		return msg;
	}

	public List<Message> readMessages(String application) {

		LinkedList<Message> messages = new LinkedList<Message>();

		List<Integer> keys = new LinkedList<Integer>(proxyBuffer.keySet());

		for (Integer k : keys) {
			Message msg = proxyBuffer.get(k);

			if (msg.getReceiver().equals(application)) {
				messages.add(proxyBuffer.remove(k));
			}
		}
		return messages;
	}

	private void invoke(Message msg) {

		Object[] param = new Object[] { msg };
		String[] sig = new String[] { msg.getClass().getName() };

		String port = "";
		String type = msg.getType();
		LinkedList<String> ops = new LinkedList<String>();

		// Adiciona a opera��o a ser execuatada no connector
		if (type.equals(Constants.REQUEST)) {
			ops.add(Constants.OP_REQUEST);
			port = Constants.PORT_INVC;
		} else if (type.equals(Constants.RESPONSE)) {
			ops.add(Constants.OP_RESPONSE);
			port = Constants.PORT_TERS;
		}
		msg.addInterceptor(msg.getConnector(), ops);

		try {
			this.mbserver.invoke(new ObjectName(msg.getConnector()), port,
					param, sig);
		} catch (Exception e) {
			System.err.println("ERRO: Invoke culd not be realizated");
			// TODO Lancar excecao
		}
	}

	/**
	 * M�todo que realiza uma busca no mbeanServer para procurar um MBean
	 * registrado com o nome do objeto passado por par�metro
	 * 
	 * @param obj
	 * @return
	 */
	private String getObjectName(String obj) throws ObjectNameNotFoundException {

		StringBuffer objeto = new StringBuffer("*:*,name=");
		objeto.append(obj);
		ObjectName scope;

		try {
			scope = new ObjectName(objeto.toString());
		} catch (Exception e) {
			throw new ObjectNameNotFoundException("Malformed Object: " + obj);
		}

		Set<ObjectName> results = this.mbserver.queryNames(scope, null);
		Iterator<ObjectName> i = results.iterator();

		if (i.hasNext()) {
			return ((ObjectName) i.next()).getCanonicalName();
		} else {
			System.err.println("ERRO: " + obj + " Not Found");
			throw new ObjectNameNotFoundException(obj);
		}
	}

	public List<String> getInterceptors() {
		try {
			ObjectName connObjName = new ObjectName(this.conn);
			List<String> ret = (List<String>) this.mbserver.invoke(connObjName,
					Constants.METH_GET_INTERCEPTORS,
					new Object[] { new Integer(1) }, new String[] { int.class
							.getName() });
			ret.add(Constants.CONN_RELIABLE);
			return ret;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/* ***** FUNCOES AUXILIARES **** */

	private boolean isConnector(String beamName) {
		if (beamName == null)
			return false;
		else {
			// Se o interceptador for Empty, significa que os interceptadores
			// acabaram
			// logo a mensagem deve ser devolvida pra o seu conector.
			return beamName.contains("Connector");
		}
	}
}